Skip to content

Floating labels: place label before control for screen readers#42539

Merged
mdo merged 1 commit into
v6-devfrom
mdo/a11y-screenreader-fix
Jun 23, 2026
Merged

Floating labels: place label before control for screen readers#42539
mdo merged 1 commit into
v6-devfrom
mdo/a11y-screenreader-fix

Conversation

@mdo

@mdo mdo commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #41362.

Floating labels currently require the control to come before the <label> in the DOM so the floating animation can use a sibling (~) selector. That ordering causes screen readers (e.g. NVDA) to announce the label after the field's value rather than before it — an accessibility concern flagged in #41362.

This switches the SCSS to look forward with :has() so the <label> can come first in the DOM (announced first by screen readers) while the CSS still reacts to the control's state (:focus, :not(:placeholder-shown), :disabled, :-webkit-autofill).

:has() has been Baseline since late 2023, which is what makes this viable for v6.

Changes

  • scss/forms/_floating-labels.scss — every control ~ label selector flipped to label:has(~ control).
  • Markup reordered to label-first (required, since the old input-first order no longer floats):
    • site/src/content/docs/forms/floating-labels.mdx (examples + explanatory note)
    • site/src/content/docs/forms/validation.mdx
    • Example pages: sign-in, heroes, dialogs, cheatsheet
    • js/tests/visual/floating-label.html
  • scss/forms/_form-control.scss — give .form-control-plaintext its own copy of the control tokens so its var(--control-*) references resolve, fixing phantom borders and label misalignment.

Notes

  • Visually a no-op for layout — the label is position: absolute, so source order doesn't change positioning.
  • This drops floating-label support for browsers without :has(); fine for v6's browserslist.
  • SCSS compiles clean and passes stylelint.

@mdo mdo requested a review from a team as a code owner June 22, 2026 05:51
@mdo mdo added this to v6.0.0 Jun 22, 2026
@github-project-automation github-project-automation Bot moved this to Inbox in v6.0.0 Jun 22, 2026
Floating labels required the control to precede the `<label>` in the DOM
so the floating animation could use a sibling (`~`) selector. That order
made screen readers (e.g. NVDA) announce the label after the field's
value instead of before it.

Switch the SCSS to look forward with `:has()` so the `<label>` can come
first in the DOM while the CSS still reacts to the control's state
(focus, value, disabled, autofill). Reorder all examples, docs, and the
visual test to label-first markup to match.

Also give `.form-control-plaintext` its own copy of the control tokens so
its `var(--control-*)` references resolve, fixing phantom borders and
label misalignment.

Fixes #41362
@mdo mdo force-pushed the mdo/a11y-screenreader-fix branch from 5bc32e4 to c1b646b Compare June 23, 2026 16:32
@mdo mdo merged commit d1c2750 into v6-dev Jun 23, 2026
12 checks passed
@mdo mdo deleted the mdo/a11y-screenreader-fix branch June 23, 2026 17:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Inbox

Development

Successfully merging this pull request may close these issues.

1 participant